---
description:
  Using and implementing custom directives to transform schema types, fields, and arguments
---

# Schema Directives

A `directive` is an identifier preceded by a `@` character, optionally followed by a list of named
arguments, which can appear after almost any form of syntax in the GraphQL query or schema
languages. Here's an example from the
[GraphQL draft specification](http://facebook.github.io/graphql/draft/#sec-Type-System.Directives)
that illustrates several of these possibilities:

```graphql
directive @deprecated(reason: String = "No longer supported") on FIELD_DEFINITION | ENUM_VALUE

type ExampleType {
  newField: String
  oldField: String @deprecated(reason: "Use `newField`.")
}
```

As you can see, the usage of `@deprecated(reason: ...){:graphql}` _follows_ the field that it
pertains to (`oldField`), though the syntax might remind you of "decorators" in other languages,
which usually appear on the line above. Directives are typically _declared_ once, using the
`directive @deprecated ... on ...{:graphql}` syntax, and then _used_ zero or more times throughout
the schema document, using the `@deprecated(reason: ...){:graphql}` syntax.

The possible applications of directive syntax are numerous: enforcing access permissions, formatting
date strings, auto-generating resolver functions for a particular backend API, marking strings for
internationalization, synthesizing globally unique object identifiers, specifying caching behavior,
skipping or including or deprecating fields, and just about anything else you can imagine.

This document focuses on directives that appear in GraphQL _schemas_ (as opposed to queries) written
in [Schema Definition Language](https://github.com/facebook/graphql/pull/90), or SDL for short. In
the following sections, you will see how custom directives can be implemented and used to modify the
structure and behavior of a GraphQL schema in ways that would not be possible using SDL syntax
alone.

## Using Schema Directives

Most of this document is concerned with _implementing_ schema directives, and some of the examples
may seem quite complicated. No matter how many tools and best practices you have at your disposal,
it can be difficult to implement a non-trivial schema directive in a reliable, reusable way.
Exhaustive testing is essential, and using a typed language like TypeScript is recommended because
there are so many different schema types to worry about.

However, the API we provide for _using_ a schema directive is extremely simple. Just import the
implementation of the directive, then pass the schema generated by `makeExecutableSchema`:

```js
import { renameDirective } from 'fake-rename-directive-package'
import { makeExecutableSchema } from '@graphql-tools/schema'

const typeDefs = /* GraphQL */ `
  type Person @rename(to: "Human") {
    name: String!
    currentDateMinusDateOfBirth: Int @rename(to: "age")
  }
`

let schema = makeExecutableSchema({
  typeDefs
})

schema = renameDirective('rename')(schema)
```

That's it. The implementation of `renameDirective` takes care of everything else. If you understand
what the directive is supposed to do to your schema, then you do not have to worry about how it
works.

For mapping multiple custom schemas you can use a reduce function like so:

```js
import { authDirective } from 'fake-auth-directive-package'
import { lowerDirective } from 'fake-lower-directive-package'
import { renameDirective } from 'fake-rename-directive-package'
import { makeExecutableSchema } from '@graphql-tools/schema'

const typeDefs = /* GraphQL */ `
  type Person @rename(to: "Human") {
    name: String!
    currentDateMinusDateOfBirth: Int @rename(to: "age")
    email: String! @auth(requires: "member") @lower
    phoneNumber: String! @auth(requires: "member")
  }
`

const directiveTransformers = [
  renameDirective('rename').renameDirectiveTransformer,
  authDirective('auth').authDirectiveTransformer,
  lowerDirective('lower').lowerDirectiveTransformer
]

let schema = makeExecutableSchema({ typeDefs })

schema = directiveTransformers.reduce((curSchema, transformer) => transformer(curSchema), schema)
```

Everything you read below addresses some aspect of how a directive like `@rename(to: ...)` could be
implemented. If that's not something you care about right now, feel free to skip the rest of this
document. When you need it, it will be here.

## Implementing Schema Directives

Since the GraphQL specification does not discuss any specific implementation strategy for
directives, it's up to each GraphQL server framework to expose an API for implementing new
directives.

GraphQL Tools provides convenient yet powerful tools for implementing directive syntax: the
[`mapSchema`](https://www.graphql-tools.com/docs/api/modules/utils_src#mapschema) and
[`getDirective`](https://www.graphql-tools.com/docs/api/modules/utils_src#getdirective) functions.
`mapSchema` takes two arguments: the original schema, and an object map -- pardon the pun -- of
functions that can be used to transform each GraphQL object within the original schema. `mapSchema`
is a powerful tool, in that it creates a new copy of the original schema, transforms GraphQL objects
as specified, and then rewires the entire schema such that all GraphQL objects that refer to other
GraphQL objects correctly point to the new set. The `getDirective` function is straightforward; it
extracts any directives (with their arguments) from the SDL originally used to create any GraphQL
object.

Here is one possible implementation of the `@deprecated` directive we saw above:

```ts
import { GraphQLSchema } from 'graphql'
import { getDirective, MapperKind, mapSchema } from '@graphql-tools/utils'

function deprecatedDirective(directiveName: string) {
  return {
    deprecatedDirectiveTypeDefs: `directive @${directiveName}(reason: String) on FIELD_DEFINITION | ENUM_VALUE`,
    deprecatedDirectiveTransformer: (schema: GraphQLSchema) =>
      mapSchema(schema, {
        [MapperKind.OBJECT_FIELD](fieldConfig) {
          const deprecatedDirective = getDirective(schema, fieldConfig, directiveName)?.[0]
          if (deprecatedDirective) {
            fieldConfig.deprecationReason = deprecatedDirective['reason']
            return fieldConfig
          }
        },
        [MapperKind.ENUM_VALUE](enumValueConfig) {
          const deprecatedDirective = getDirective(schema, enumValueConfig, directiveName)?.[0]
          if (deprecatedDirective) {
            enumValueConfig.deprecationReason = deprecatedDirective['reason']
            return enumValueConfig
          }
        }
      })
  }
}
```

To apply this implementation to a schema that contains `@deprecated` directives, simply pass the
necessary typeDefs and schema transformation function to the `makeExecutableSchema` function in the
appropriate positions:

```ts
import { deprecatedDirective } from 'fake-deprecated-directive-package'
import { makeExecutableSchema } from '@graphql-tools/schema'

const { deprecatedDirectiveTypeDefs, deprecatedDirectiveTransformer } =
  deprecatedDirective('deprecated')

let schema = makeExecutableSchema({
  typeDefs: [
    deprecatedDirectiveTypeDefs,
    /* GraphQL */ `
      type ExampleType {
        newField: String
        oldField: String @deprecated(reason: "Use \`newField\`.")
      }

      type Query {
        rootField: ExampleType
      }
    `
  ]
})
schema = deprecatedDirectiveTransformer(schema)
```

We suggest that creators of directive-based schema modification functions allow users to customize
the names of the relevant directives, to help users avoid the collision of directive names with
existing directives within their schema or other external schema modification functions. Of course,
you could hard-code the name of the directive into the function, further simplifying the above
examples.

## Examples

To appreciate the range of possibilities enabled by `mapSchema`, let's examine a variety of
practical examples.

### Uppercasing Strings

Suppose you want to ensure a string-valued field is converted to uppercase. Though this use case is
simple, it's a good example of a directive implementation that works by wrapping a field's `resolve`
function:

```ts
import { defaultFieldResolver, GraphQLSchema } from 'graphql'
import { makeExecutableSchema } from '@graphql-tools/schema'
import { getDirective, MapperKind, mapSchema } from '@graphql-tools/utils'

function upperDirective(directiveName: string): (schema: GraphQLSchema) => GraphQLSchema {
  return schema =>
    mapSchema(schema, {
      [MapperKind.OBJECT_FIELD]: fieldConfig => {
        const upperDirective = getDirective(schema, fieldConfig, directiveName)?.[0]
        if (upperDirective) {
          const { resolve = defaultFieldResolver } = fieldConfig
          return {
            ...fieldConfig,
            resolve: async function (source, args, context, info) {
              const result = await resolve(source, args, context, info)
              if (typeof result === 'string') {
                return result.toUpperCase()
              }
              return result
            }
          }
        }
      }
    })
}

const upperDirectiveTypeDefs = (directiveName: string) => /* GraphQL */ `
  directive @${directiveName} on FIELD_DEFINITION
`
const applyUpperSchemaTransform = upperDirective('upper')

let schema = makeExecutableSchema({
  typeDefs: [
    upperDirectiveTypeDefs('upper'),
    /* GraphQL */ `
      type Query {
        hello: String @upper
        hello2: String @upperCase
      }
    `
  ],
  resolvers: {
    Query: {
      hello() {
        return 'hello world'
      },
      hello2() {
        return 'hello world'
      }
    }
  }
})

schema = applyUpperSchemaTransform(schema)
```

Notice how easy it is to handle both `@upper` and `@upperCase` with the same `upperDirective`
implementation.

### Fetching Data from a REST API

Suppose you've defined an object type that corresponds to a
[REST](https://en.wikipedia.org/wiki/Representational_state_transfer) resource, and you want to
avoid implementing resolver functions for every field:

```ts
function restDirective(directiveName: string) {
  return {
    restDirectiveTypeDefs: `directive @${directiveName}(url: String) on FIELD_DEFINITION`,
    restDirectiveTransformer: (schema: GraphQLSchema) =>
      mapSchema(schema, {
        [MapperKind.OBJECT_FIELD](fieldConfig) {
          const restDirective = getDirective(schema, fieldConfig, directiveName)?.[0]
          if (restDirective) {
            const { url } = restDirective
            fieldConfig.resolve = () => fetch(url)
            return fieldConfig
          }
        }
      })
  }
}

const { restDirectiveTypeDefs, restDirectiveTransformer } = restDirective('rest')

let schema = makeExecutableSchema({
  typeDefs: [
    restDirectiveTypeDefs,
    /* GraphQL */ `
      type Query {
        people: [Person] @rest(url: "/api/v1/people")
      }
    `
  ]
})

schema = restDirectiveTransformer(schema)
```

There are many more issues to consider when implementing a real GraphQL wrapper over a REST endpoint
(such as how to do caching or pagination), but this example demonstrates the basic structure.

### Formatting date strings

Suppose your resolver returns a `Date` object, but you want to return a formatted string to the
client:

```ts
function dateDirective(directiveName: string) {
  return {
    dateDirectiveTypeDefs: `directive @${directiveName}(format: String) on FIELD_DEFINITION`,
    dateDirectiveTransformer: (schema: GraphQLSchema) =>
      mapSchema(schema, {
        [MapperKind.OBJECT_FIELD](fieldConfig) {
          const dateDirective = getDirective(schema, fieldConfig, directiveName)?.[0]
          if (dateDirective) {
            const { resolve = defaultFieldResolver } = fieldConfig
            const { format } = dateDirective
            fieldConfig.resolve = async (source, args, context, info) => {
              const date = await resolve(source, args, context, info)
              return formatDate(date, format, true)
            }
            return fieldConfig
          }
        }
      })
  }
}

const { dateDirectiveTypeDefs, dateDirectiveTransformer } = dateDirective('date')

let schema = makeExecutableSchema({
  typeDefs: [
    dateDirectiveTypeDefs,
    /* GraphQL */ `
      scalar Date

      type Query {
        today: Date @date(format: "mmmm d, yyyy")
      }
    `
  ],
  resolvers: {
    Query: {
      today() {
        return new Date(1519688273858).toUTCString()
      }
    }
  }
})
schema = dateDirectiveTransformer(schema)
```

Of course, it would be even better if the schema author did not have to decide on a specific `Date`
format, but could instead leave that decision to the client. To make this work, the directive just
needs to add an additional argument to the field:

```ts
import formatDate from 'dateformat'

function formattableDateDirective(directiveName: string) {
  return {
    formattableDateDirectiveTypeDefs: `directive @${directiveName}(
        defaultFormat: String = "mmmm d, yyyy"
      ) on FIELD_DEFINITION
    `,
    formattableDateDirectiveTransformer: (schema: GraphQLSchema) =>
      mapSchema(schema, {
        [MapperKind.OBJECT_FIELD](fieldConfig) {
          const dateDirective = getDirective(schema, fieldConfig, directiveName)?.[0]
          if (dateDirective) {
            const { resolve = defaultFieldResolver } = fieldConfig
            const { defaultFormat } = dateDirective

            if (!fieldConfig.args) {
              throw new Error('Unexpected Error. args should be defined.')
            }

            fieldConfig.args['format'] = {
              type: GraphQLString
            }

            fieldConfig.type = GraphQLString
            fieldConfig.resolve = async (source, { format, ...args }, context, info) => {
              const newFormat = format || defaultFormat
              const date = await resolve(source, args, context, info)
              return formatDate(date, newFormat, true)
            }
            return fieldConfig
          }
        }
      })
  }
}

const { formattableDateDirectiveTypeDefs, formattableDateDirectiveTransformer } =
  formattableDateDirective('date')

let schema = makeExecutableSchema({
  typeDefs: [
    formattableDateDirectiveTypeDefs,
    /* GraphQL */ `
      scalar Date

      type Query {
        today: Date @date
      }
    `
  ],
  resolvers: {
    Query: {
      today() {
        return new Date(1521131357195)
      }
    }
  }
})
schema = formattableDateDirectiveTransformer(schema)
```

Now the client can specify a desired `format` argument when requesting the `Query.today` field, or
omit the argument to use the `defaultFormat` string specified in the schema:

```js
import { graphql } from 'graphql'

graphql(
  schema,
  /* GraphQL */ `
    query {
      today
    }
  `
).then(result => {
  // Logs with the default "mmmm d, yyyy" format:
  console.log(result.data.today)
})

graphql(
  schema,
  /* GraphQL */ `
    query {
      today(format: "d mmm yyyy")
    }
  `
).then(result => {
  // Logs with the requested "d mmm yyyy" format:
  console.log(result.data.today)
})
```

### Enforcing Access Permissions

Imagine a hypothetical `@auth` directive that takes an argument `requires` of type `Role`, which
defaults to `ADMIN`. This `@auth` directive can appear on an `OBJECT` like `User` to set default
access permissions for all `User` fields, as well as appearing on individual fields, to enforce
field-specific `@auth` restrictions:

```graphql
directive @auth(requires: Role = ADMIN) on OBJECT | FIELD_DEFINITION

enum Role {
  ADMIN
  REVIEWER
  USER
  UNKNOWN
}

type User @auth(requires: USER) {
  name: String
  banned: Boolean @auth(requires: ADMIN)
  canPost: Boolean @auth(requires: REVIEWER)
}
```

```ts
function authDirective(
  directiveName: string,
  getUserFn: (token: string) => { hasRole: (role: string) => boolean }
) {
  const typeDirectiveArgumentMaps: Record<string, any> = {}
  return {
    authDirectiveTypeDefs: `directive @${directiveName}(
      requires: Role = ADMIN,
    ) on OBJECT | FIELD_DEFINITION

    enum Role {
      ADMIN
      REVIEWER
      USER
      UNKNOWN
    }`,
    authDirectiveTransformer: (schema: GraphQLSchema) =>
      mapSchema(schema, {
        [MapperKind.TYPE]: type => {
          const authDirective = getDirective(schema, type, directiveName)?.[0]
          if (authDirective) {
            typeDirectiveArgumentMaps[type.name] = authDirective
          }
          return undefined
        },
        [MapperKind.OBJECT_FIELD]: (fieldConfig, _fieldName, typeName) => {
          const authDirective =
            getDirective(schema, fieldConfig, directiveName)?.[0] ??
            typeDirectiveArgumentMaps[typeName]
          if (authDirective) {
            const { requires } = authDirective
            if (requires) {
              const { resolve = defaultFieldResolver } = fieldConfig
              fieldConfig.resolve = function (source, args, context, info) {
                const user = getUserFn(context.headers.authToken)
                if (!user.hasRole(requires)) {
                  throw new Error('not authorized')
                }
                return resolve(source, args, context, info)
              }
              return fieldConfig
            }
          }
        }
      })
  }
}

function getUser(token: string) {
  const roles = ['UNKNOWN', 'USER', 'REVIEWER', 'ADMIN']
  return {
    hasRole: (role: string) => {
      const tokenIndex = roles.indexOf(token)
      const roleIndex = roles.indexOf(role)
      return roleIndex >= 0 && tokenIndex >= roleIndex
    }
  }
}

const { authDirectiveTypeDefs, authDirectiveTransformer } = authDirective('auth', getUser)

let schema = makeExecutableSchema({
  typeDefs: [
    authDirectiveTypeDefs,
    /* GraphQL */ `
      type User @auth(requires: USER) {
        name: String
        banned: Boolean @auth(requires: ADMIN)
        canPost: Boolean @auth(requires: REVIEWER)
      }

      type Query {
        users: [User]
      }
    `
  ],
  resolvers: {
    Query: {
      users: () => [
        {
          banned: true,
          canPost: false,
          name: 'Ben'
        }
      ]
    }
  }
})
schema = authDirectiveTransformer(schema)
```

One drawback of this approach is that it does not guarantee fields will be wrapped if they are added
to the schema after `AuthDirective` is applied, and the whole `getUser(context.headers.authToken)`
is a made-up API that would need to be fleshed out. In other words, we’ve glossed over some of the
details that would be required for a production-ready implementation of this directive, though we
hope the basic structure shown here inspires you to find clever solutions to the remaining problems.

### Enforcing Value Restrictions

Suppose you want to enforce a maximum length for a string-valued field:

```ts
function lengthDirective(directiveName: string) {
  class LimitedLengthType extends GraphQLScalarType {
    constructor(type: GraphQLScalarType, maxLength: number) {
      super({
        name: `${type.name}WithLengthAtMost${maxLength}`,

        serialize(value: string) {
          const newValue: string = type.serialize(value)
          expect(typeof newValue.length).toBe('number')
          if (newValue.length > maxLength) {
            throw new Error(
              `expected ${newValue.length.toString(10)} to be at most ${maxLength.toString(10)}`
            )
          }
          return newValue
        },

        parseValue(value: string) {
          return type.parseValue(value)
        },

        parseLiteral(ast) {
          return type.parseLiteral(ast, {})
        }
      })
    }
  }

  const limitedLengthTypes: Record<string, Record<number, GraphQLScalarType>> = {}

  function getLimitedLengthType(type: GraphQLScalarType, maxLength: number): GraphQLScalarType {
    const limitedLengthTypesByTypeName = limitedLengthTypes[type.name]
    if (!limitedLengthTypesByTypeName) {
      const newType = new LimitedLengthType(type, maxLength)
      limitedLengthTypes[type.name] = {}
      limitedLengthTypes[type.name][maxLength] = newType
      return newType
    }

    const limitedLengthType = limitedLengthTypesByTypeName[maxLength]
    if (!limitedLengthType) {
      const newType = new LimitedLengthType(type, maxLength)
      limitedLengthTypesByTypeName[maxLength] = newType
      return newType
    }

    return limitedLengthType
  }

  function wrapType<F extends GraphQLFieldConfig<any, any> | GraphQLInputFieldConfig>(
    fieldConfig: F,
    directiveArgumentMap: Record<string, any>
  ): void {
    if (isNonNullType(fieldConfig.type) && isScalarType(fieldConfig.type.ofType)) {
      fieldConfig.type = getLimitedLengthType(fieldConfig.type.ofType, directiveArgumentMap['max'])
    } else if (isScalarType(fieldConfig.type)) {
      fieldConfig.type = getLimitedLengthType(fieldConfig.type, directiveArgumentMap['max'])
    } else {
      throw new Error(`Not a scalar type: ${fieldConfig.type.toString()}`)
    }
  }

  return {
    lengthDirectiveTypeDefs: `directive @${directiveName}(max: Int) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION`,
    lengthDirectiveTransformer: (schema: GraphQLSchema) =>
      mapSchema(schema, {
        [MapperKind.FIELD]: fieldConfig => {
          const lengthDirective = getDirective(schema, fieldConfig, directiveName)?.[0]
          if (lengthDirective) {
            wrapType(fieldConfig, lengthDirective)
            return fieldConfig
          }
        }
      })
  }
}

const { lengthDirectiveTypeDefs, lengthDirectiveTransformer } = lengthDirective('length')

let schema = makeExecutableSchema({
  typeDefs: [
    lengthDirectiveTypeDefs,
    /* GraphQL */ `
      type Query {
        books: [Book]
      }

      type Book {
        title: String @length(max: 10)
      }

      type Mutation {
        createBook(book: BookInput): Book
      }

      input BookInput {
        title: String! @length(max: 10)
      }
    `
  ],
  resolvers: {
    Query: {
      books: () => [{ title: 'abcdefghijklmnopqrstuvwxyz' }]
    },
    Mutation: {
      createBook: (_parent, args) => args.book
    }
  }
})
schema = lengthDirectiveTransformer(schema)
```

Note that new types can be added to the schema with ease, but that each type must be uniquely named.

### Synthesizing Unique IDs

Suppose your database uses incrementing IDs for each resource type, so IDs are not unique across all
resource types. Here’s how you might synthesize a field called `uid` that combines the object type
with various field values to produce an ID that’s unique across your schema:

```ts
import { createHash } from 'crypto'
import { GraphQLID } from 'graphql'

function uniqueIDDirective(directiveName: string) {
  return {
    uniqueIDDirectiveTypeDefs: `directive @${directiveName}(name: String, from: [String]) on OBJECT`,
    uniqueIDDirectiveTransformer: (schema: GraphQLSchema) =>
      mapSchema(schema, {
        [MapperKind.OBJECT_TYPE]: type => {
          const uniqueIDDirective = getDirective(schema, type, directiveName)?.[0]
          if (uniqueIDDirective) {
            const { name, from } = uniqueIDDirective
            const config = type.toConfig()
            config.fields[name] = {
              type: GraphQLID,
              description: 'Unique ID',
              args: {},
              resolve(object: any) {
                const hash = createHash('sha1')
                hash.update(type.name)
                for (const fieldName of from) {
                  hash.update(String(object[fieldName]))
                }
                return hash.digest('hex')
              }
            }
            return new GraphQLObjectType(config)
          }
        }
      })
  }
}

const { uniqueIDDirectiveTypeDefs, uniqueIDDirectiveTransformer } = uniqueIDDirective('uniqueID')

let schema = makeExecutableSchema({
  typeDefs: [
    uniqueIDDirectiveTypeDefs,
    /* GraphQL */ `
      type Query {
        people: [Person]
        locations: [Location]
      }

      type Person @uniqueID(name: "uid", from: ["personID"]) {
        personID: Int
        name: String
      }

      type Location @uniqueID(name: "uid", from: ["locationID"]) {
        locationID: Int
        address: String
      }
    `
  ],
  resolvers: {
    Query: {
      people: () => [
        {
          personID: 1,
          name: 'Ben'
        }
      ],
      locations: () => [
        {
          locationID: 1,
          address: '140 10th St'
        }
      ]
    }
  }
})
schema = uniqueIDDirectiveTransformer(schema)
```

### GraphQL modules usage

If you create your application with GraphQL modules please use the `schemaBuilder` parameter in
`createApplication()` to apply the directive implementation:

```ts
import { anyDirective } from 'any-directive-package'
import { makeExecutableSchema } from '@graphql-tools/schema'

const application = createApplication({
    ...
    schemaBuilder: ({ typeDefs, resolvers }) => {
        // Build the schema from modules
        let schema = makeExecutableSchema({
            typeDefs,
            resolvers: resolvers
        })

        // Apply the directive transformer, i.e. rename directive
        const patchedSchema = anyDirective('rename')(schema);

        return patchedSchema;
    }
})
```

## Declaring Schema Directives

SDL syntax requires declaring the names, argument types, default argument values, and permissible
locations of any available directives. We have shown one approach above to doing so. If you're
implementing a reusable directive for public consumption, you will probably want to either guide
your users as to how properly declare their directives, or export the required SDL syntax as above
so that users can pass it to `makeExecutableSchema`. These techniques can be used in combination,
i.e. you may wish to export the directive syntax and provide instructions on how to structure any
dependent types. Take a second look at the auth example above to see how this may be done and note
the interplay between the directive definition and the `Role` type.

## What about Query Directives?

The directive syntax can also appear in GraphQL queries sent from the client. Query directive
implementation can be performed within GraphQL resolver using similar techniques as the above. In
general, however, schema authors should consider using field arguments wherever possible instead of
query directives, with query directives most useful for annotating the query with metadata affecting
the execution algorithm itself, e.g.
[`defer`, `stream`](https://github.com/graphql/graphql-wg/blob/main/rfcs/DeferStream.md), etc.

In theory, access to the query directives is available within the `info` resolver argument by
iterating through each `fieldNode` of `info.fieldNodes`, although, as above use of query directives
within standard resolvers is not necessarily recommended.

## What about `directiveResolvers`?

The `makeExecutableSchema` function is used to take a `directiveResolvers` option that could be used
for implementing certain kinds of `@directive`s on fields that have resolver functions.

The new abstraction is more general since it can visit any kind of schema syntax, and do much more
than just wrap resolver functions. The old `directiveResolvers` API can be implemented with the
above new API as follows:

```ts
export function attachDirectiveResolvers(
  schema: GraphQLSchema,
  directiveResolvers: IDirectiveResolvers
): GraphQLSchema {
  // ... argument validation ...

  return mapSchema(schema, {
    [MapperKind.OBJECT_FIELD]: fieldConfig => {
      const newFieldConfig = { ...fieldConfig }

      const directives = getDirectives(schema, fieldConfig)
      for (const directive of directives) {
        const directiveName = directive.name
        if (directiveResolvers[directiveName]) {
          const resolver = directiveResolvers[directiveName]
          const originalResolver =
            newFieldConfig.resolve != null ? newFieldConfig.resolve : defaultFieldResolver
          const directiveArgs = directive.args
          newFieldConfig.resolve = (source, originalArgs, context, info) => {
            return resolver(
              () =>
                new Promise((resolve, reject) => {
                  const result = originalResolver(source, originalArgs, context, info)
                  if (result instanceof Error) {
                    reject(result)
                  }
                  resolve(result)
                }),
              source,
              directiveArgs,
              context,
              info
            )
          }
        }
      }

      return newFieldConfig
    }
  })
}
```

## What about Code-First Schemas?

You can use schema transformation functions with code-first schemas as well. By default, if a
`directives` key exists within the `extensions` field for a given GraphQL entity, the
[`getDirectives`](https://graphql-tools.com/docs/api/modules/utils_src#getdirectives) function will
retrieve the directive data from the GraphQL entity's `extensions.directives` data rather than from
the SDL. This, of course, allows schemas created without SDL to use any schema transformation
functions created for directive use, as long as they define the necessary data within the GraphQL
entity extensions.

This behavior can be customized! The `getDirectives` function takes a third argument,
`pathToDirectivesInExtensions`, an array of strings, that allows customization of this path to
directive data within extensions, which is set to `['directives']` by default. We recommend allowing
end users to customize this path similar to how the directive name can be customized above.

See [this `graphql-js` issue](https://github.com/graphql/graphql-js/issues/1343) for more
information on directives with code-first schemas. We follow the
[Gatsby and graphql-compose convention](https://github.com/graphql/graphql-js/issues/1343#issuecomment-479877640)
of reading directives from the `extensions` field, but allow customization as above.

## Full `mapSchema` API

How can you customize schema mapping? The second argument provided to `mapSchema` is an object of
type `SchemaMapper` that can specify individual mapping functions.

GraphQL's objects are mapped according to the following algorithm:

1. Types are mapped. The most general matching mapping function available will be used, i.e.
   inclusion of a `MapperKind.TYPE` will cause all types to be mapped with the specified mapper.
   Specifying `MapperKind.ABSTRACT_TYPE` and `MapperKind.MAPPER.QUERY` mappers will cause the first
   mapper to be used for interfaces and unions, the latter to be used for the root query object
   type, and all other types to be ignored.
2. Enum values are mapped. If all you want to do to an enum is to change one value, it is more
   convenient to use a `MapperKind.ENUM_VALUE` mapper than to iterate through all values on your own
   and recreate the type – although that would work!
3. Fields are mapped. Similar to above, if you want to modify a single field, `mapSchema` can do the
   iteration for you. You can subspecify `MapperKind.OBJECT_FIELD` or `MapperKind.ROOT_FIELD` to
   select a limited subset of fields to map.
4. Arguments are mapped. Similar to above, you can subspecify `MapperKind.ARGUMENT` if you want to
   modify only an argument. `mapSchema` can iterate through the types and fields for you.
5. Directives are mapped if `MapperKind.DIRECTIVE` is specified.

```ts
export interface SchemaMapper {
  [MapperKind.TYPE]?: NamedTypeMapper
  [MapperKind.SCALAR_TYPE]?: ScalarTypeMapper
  [MapperKind.ENUM_TYPE]?: EnumTypeMapper
  [MapperKind.COMPOSITE_TYPE]?: CompositeTypeMapper
  [MapperKind.OBJECT_TYPE]?: ObjectTypeMapper
  [MapperKind.INPUT_OBJECT_TYPE]?: InputObjectTypeMapper
  [MapperKind.ABSTRACT_TYPE]?: AbstractTypeMapper
  [MapperKind.UNION_TYPE]?: UnionTypeMapper
  [MapperKind.INTERFACE_TYPE]?: InterfaceTypeMapper
  [MapperKind.ROOT_OBJECT]?: ObjectTypeMapper
  [MapperKind.QUERY]?: ObjectTypeMapper
  [MapperKind.MUTATION]?: ObjectTypeMapper
  [MapperKind.SUBSCRIPTION]?: ObjectTypeMapper
  [MapperKind.ENUM_VALUE]?: EnumValueMapper
  [MapperKind.FIELD]?: GenericFieldMapper<GraphQLFieldConfig<any, any> | GraphQLInputFieldConfig>
  [MapperKind.OBJECT_FIELD]?: FieldMapper
  [MapperKind.ROOT_FIELD]?: FieldMapper
  [MapperKind.QUERY_ROOT_FIELD]?: FieldMapper
  [MapperKind.MUTATION_ROOT_FIELD]?: FieldMapper
  [MapperKind.SUBSCRIPTION_ROOT_FIELD]?: FieldMapper
  [MapperKind.INTERFACE_FIELD]?: FieldMapper
  [MapperKind.COMPOSITE_FIELD]?: FieldMapper
  [MapperKind.INPUT_OBJECT_FIELD]?: InputFieldMapper
  [MapperKind.ARGUMENT]?: ArgumentMapper
  [MapperKind.DIRECTIVE]?: DirectiveMapper
}

export type NamedTypeMapper = (
  type: GraphQLNamedType,
  schema: GraphQLSchema
) => GraphQLNamedType | null | undefined

export type ScalarTypeMapper = (
  type: GraphQLScalarType,
  schema: GraphQLSchema
) => GraphQLScalarType | null | undefined

export type EnumTypeMapper = (
  type: GraphQLEnumType,
  schema: GraphQLSchema
) => GraphQLEnumType | null | undefined

export type EnumValueMapper = (
  value: GraphQLEnumValueConfig,
  typeName: string,
  schema: GraphQLSchema
) => GraphQLEnumValueConfig | [string, GraphQLEnumValueConfig] | null | undefined

export type CompositeTypeMapper = (
  type: GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType,
  schema: GraphQLSchema
) => GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType | null | undefined

export type ObjectTypeMapper = (
  type: GraphQLObjectType,
  schema: GraphQLSchema
) => GraphQLObjectType | null | undefined

export type InputObjectTypeMapper = (
  type: GraphQLInputObjectType,
  schema: GraphQLSchema
) => GraphQLInputObjectType | null | undefined

export type AbstractTypeMapper = (
  type: GraphQLInterfaceType | GraphQLUnionType,
  schema: GraphQLSchema
) => GraphQLInterfaceType | GraphQLUnionType | null | undefined

export type UnionTypeMapper = (
  type: GraphQLUnionType,
  schema: GraphQLSchema
) => GraphQLUnionType | null | undefined

export type InterfaceTypeMapper = (
  type: GraphQLInterfaceType,
  schema: GraphQLSchema
) => GraphQLInterfaceType | null | undefined

export type DirectiveMapper = (
  directive: GraphQLDirective,
  schema: GraphQLSchema
) => GraphQLDirective | null | undefined

export type GenericFieldMapper<F extends GraphQLFieldConfig<any, any> | GraphQLInputFieldConfig> = (
  fieldConfig: F,
  fieldName: string,
  typeName: string,
  schema: GraphQLSchema
) => F | [string, F] | null | undefined

export type FieldMapper = GenericFieldMapper<GraphQLFieldConfig<any, any>>

export type ArgumentMapper = (
  argumentConfig: GraphQLArgumentConfig,
  fieldName: string,
  typeName: string,
  schema: GraphQLSchema
) => GraphQLArgumentConfig | [string, GraphQLArgumentConfig] | null | undefined

export type InputFieldMapper = GenericFieldMapper<GraphQLInputFieldConfig>
```
